package com.recommended.util;

import com.recommended.dto.DataInfoId;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.CachingRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.IDRescorer;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Component
public class DataRecommendedUtil {
    /**
     * 邻居数量
     */
    final private int NEIGHBORHOOD_NUM = 5;
    /**
     * 资料对应专利、学校数据
     */
    private final static String PROFESSIONAL_DATA_PATH = "mahout/data_professional.data";

    /**
     * 数据模型
     */
    @Resource(name = "fileDataModel")
    private DataModel dataModel;
    /**
     * 资料对应专利、学校数据
     */
    private static List<DataInfoId> dataInfoIdList = new ArrayList<>();

    @PostConstruct
    public void initDataInfoIds() {
        BufferedReader br = null;
        try {
            URL url = DataRecommendedUtil.class.getClassLoader().getResource(PROFESSIONAL_DATA_PATH);
            br = new BufferedReader(new FileReader(url.getFile()));
            String s = null;
            while ((s = br.readLine()) != null) {
                String[] cols = s.split(",");
                DataInfoId dataInfoId = new DataInfoId();
                dataInfoId.setDataId(Long.valueOf(cols[0]));
                dataInfoId.setProfessionalId(Integer.valueOf(cols[1]));
                dataInfoId.setUniversityId(Integer.valueOf(cols[2]));
                dataInfoIdList.add(dataInfoId);
            }
            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 基于用户相似用户推荐
     *
     * @param userId       用户id
     * @param size         推荐结果数量
     * @param professional 专业
     * @param university   学校
     * @return
     * @throws TasteException
     */
    public List<Long> userBasedRecommended(long userId, int size, Integer professional, Integer university) throws TasteException {
        // 欧氏距离的相似度
        UserSimilarity similarity = new EuclideanDistanceSimilarity(dataModel);
        NearestNUserNeighborhood neighbor = new NearestNUserNeighborhood(NEIGHBORHOOD_NUM, similarity, dataModel);
        Recommender recommender = new CachingRecommender(new GenericUserBasedRecommender(dataModel, neighbor, similarity));
        return getRecommendedItemIDs(getRecommendedItem(recommender, userId, size, professional, university));
    }

    /**
     * 基于相似实体推荐
     *
     * @param userId       用户id
     * @param size         推荐结果数量
     * @param professional 专业
     * @param university   学校
     * @return
     * @throws TasteException
     */
    public List<Long> itemBasedRecommended(long userId, int size, Integer professional, Integer university) throws TasteException {
        // 皮尔逊相关系数的相似度
        ItemSimilarity itemSimilarity = new PearsonCorrelationSimilarity(dataModel);
        Recommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity);
        return getRecommendedItemIDs(getRecommendedItem(recommender, userId, size, professional, university));
    }

    /**
     * 获取推荐结果
     *
     * @param recommender  推荐器
     * @param userId       用户id
     * @param size
     * @param professional
     * @param university
     * @return
     * @throws TasteException
     */
    private static List<RecommendedItem> getRecommendedItem(Recommender recommender, long userId, int size, Integer professional, Integer university) throws TasteException {
        Set<Long> dataIds = getOutProfessionalDataIds(professional, university);
        IDRescorer idRescorer = new ProfessionalRescorer(dataIds);
        return recommender.recommend(userId, size, idRescorer);
    }

    /**
     * 获取推荐结果id：资料id
     *
     * @param recommendations
     * @return
     */
    private List<Long> getRecommendedItemIDs(List<RecommendedItem> recommendations) {
        List<Long> recommendItems = new ArrayList<>();
        for (RecommendedItem recommendedItem : recommendations) {
            recommendItems.add(recommendedItem.getItemID());
        }
        return recommendItems;
    }

    /**
     * 根据专业、学校进行筛选
     *
     * @param professionalId 专业id
     * @param universityId   学校id
     * @return
     */
    public static Set<Long> getOutProfessionalDataIds(Integer professionalId, Integer universityId) {
        Set<Long> dataIds = new HashSet<>();
        for (DataInfoId dataInfoId : dataInfoIdList) {
            if (professionalId != null && universityId != null) {
                if (dataInfoId.getProfessionalId() != professionalId.intValue() || dataInfoId.getUniversityId() != universityId.intValue()) {
                    dataIds.add(dataInfoId.getDataId());
                }
            } else if (professionalId != null && universityId == null) {
                if (professionalId.intValue() != dataInfoId.getProfessionalId()) {
                    dataIds.add(dataInfoId.getDataId());
                }
            } else if (professionalId == null && universityId != null) {
                if (universityId.intValue() != dataInfoId.getUniversityId()) {
                    dataIds.add(dataInfoId.getDataId());
                }
            }
        }
        return dataIds;
    }
}
